#include "redmapobject.h"
#include "redsuperobject.h"
#include "redtupleobject.h"
#include "redstringobject.h"
#include "redexceptionobject.h"
#include "redboundmethodobject.h"

static char super_init(RedObject *self, RedObject *args)
{
	if (!Red_INIT())
		return 0;

	if (!RedTuple_Exact(args))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'tuple' expected for 'args' argument");
		return 0;
	}

	Red_REF(RedTuple_Items(args)[0]);
	RedSuper_Self(self) = RedTuple_Items(args)[0];
	return 1;
}

static void super_clear(RedObject *self)
{
	Red_UNREF(RedSuper_Self(self));
}

static void super_release(RedObject *self)
{
	super_clear(self);
	Red_RELEASE();
}

static void super_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	Red_VISIT(RedSuper_Self(self));
}

static RedObject *super_delattr(RedObject *self, RedObject *attr)
{
	RedException_ThrowFormat(&RedExc_AttributeError, "Cannot '__delattr__' for 'super' objects");
	return NULL;
}

static RedObject *super_getattr(RedObject *self, RedObject *attr)
{
	if (!RedString_Exact(attr))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "Attribute name must be 'string'");
		return NULL;
	}

	/* resolve attribute in parent class */
	RedTypeObject *prev = NULL;
	RedTypeObject *type = RedSuper_Self(self)->ob_type->tp_parent;

	/* `object`->tp_parent == `object` */
	while (type != prev)
	{
		RedObject *method = RedMap_GetString(type->ob_head.ob_attrs, attr);

		/* class level method must be bound with `self` */
		if (method && RedObject_Callable(method))
		{
			Red_REF(method);
			return RedBoundMethod_BindSelf(method, RedSuper_Self(self));
		}

		Red_UNREF(method);
		prev = type;
		type = type->tp_parent;
	}

	/* not found, propagate to parent */
	return RedSuper_Self(self)->ob_type->tp_parent->tp_getattr(RedSuper_Self(self), attr);
}

static RedObject *super_setattr(RedObject *self, RedObject *attr, RedObject *value)
{
	RedException_ThrowFormat(&RedExc_AttributeError, "Cannot '__delattr__' for 'super' objects");
	return NULL;
}

RedTypeObject RedType_Super =
{
	RedType_INIT("super", RedSuperObject, RedType_Object)

	.tp_init		= super_init,
	.tp_clear		= super_clear,
	.tp_release		= super_release,
	.tp_traverse	= super_traverse,

	.tp_delattr		= super_delattr,
	.tp_getattr		= super_getattr,
	.tp_setattr		= super_setattr,
};
